// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "filesys.h"
#include "bitmap.h"
#include "addrspace.h"
#include "machine.h"

extern void Copy(char *unixFile, char *nachosFile);
extern void Append(char *unixFile, char *nachosFile, int half);
extern void NAppend(char *nachosFileFrom, char *nachosFileTo);
extern void Print(char *file);
extern void NAppend(char *nachosFileFrom, char *nachosFileTo);
extern void StartProcess(char *file);
extern void StartProcess(int arg);
extern void MakeDir(char* name);
extern void PerformanceTest(void);



//-------------------------------------------------------------------------
// delete all char 'c' in str
//
//-------------------------------------------------------------------------
char buf[128];
char *EraseStrChar(char *str,char c){
    int i = 0;
//printf("str=%s\n",str);
    while (*str != '\0'){
        if (*str != c){
            buf[i] = *str;
            str++;
            i++;
        } //if
        else
            str++;
    } //while
    buf[i] = '\0';
    //printf("buf=%s\n",buf);
    return buf;
}





//----------------------------------------------------------------------
// Excep_SC_Exec  
//  创建一个内核线程，将新的用户线程映射到内核线程上
//----------------------------------------------------------------------

void Excep_SC_Exec(){
    // 读入参数
    // 参数的地址保留在 $4 寄存器中
   
    int addr = machine->ReadRegister(4);
    char filename[128];
    // 到内存中，读出可执行文件名
    int i =0;
    do{
        //read filename from mainMemory
        machine->ReadMem(addr+i,1,(int *)&filename[i]);
    }while(filename[i++]!='\0');

    //---------------------------------------------------------
    //
    // inner commands --begin
    //
    //---------------------------------------------------------
    if(filename[0] == 'l' && filename[1] == 's'){       //ls
        // 列出目录
        fileSystem->List();
       
         // 放开后面的阻塞
        machine->WriteRegister(2,PidNum);
    }
    else if (filename[0] == 'r' && filename[1] == 'm'){// rm
        char fn[128];
        strncpy(fn,filename,strlen(filename));
        fn[strlen(filename)] = '\0';
        fn[0] = ' ';
        fn[1] = ' ';
        char *file = EraseStrChar(fn,' ');
        if(file != NULL && strlen(fn) > 0){
            fileSystem->Remove(file);
            machine->WriteRegister(2,PidNum);
        }else{
            printf("Remove: invalid file name.\n");
            machine->WriteRegister(2,-1);
        }
    }
    else if(filename[0] == 'c'&& filename[1] == 'a' && filename[2] =='t'){  // cat file
        char fn[128];
        strncpy(fn,filename,strlen(filename));
        fn[strlen(filename)] = '\0';
        fn[0] = ' ';
        fn[1] = ' ';
        fn[2] = ' ';
        char *file = EraseStrChar(fn,' ');
        //printf("filename=%s, fn=%s, file=%s\n",filename,fn, file);
        if (file != NULL && strlen(file) >0)
        {
            Print(file);
            machine->WriteRegister(2,PidNum);
        }
        else{
        printf("Cat: file not exists.\n");
        machine->WriteRegister(2,-1);
        }

    }
    else if(filename[0] == 'c' && filename[1] == 'f'){ // create a noches file
        char fn[128];
        strncpy(fn,filename,strlen(filename));
        fn[strlen(filename)] = '\0';
        fn[0] = ' ';
        fn[1] = ' ';
        char *file = EraseStrChar(fn,' ');
        if (file != NULL && strlen(file) >0){
            fileSystem->Create(file,0);
            machine->WriteRegister(2,PidNum);
        }
        else{
            printf("Create: file already exists.\n");
            machine->WriteRegister(2,-1);
        }
    }
    else if((filename[0] == 'c' && filename[1] == 'p') ||                    // nap source dest
         (filename[0] == 'n' && filename[1] == 'a' && filename[2] == 'p')) { // cp source dest
     //Copy(*(argv + 1), *(argv + 2));
        // nachos文件系统内cp
        char source[128];
        char dest[128];
        bool flag1 = true;
        bool flag2 = true;
        int i;
        int j = 0;
        for( i = 3;i<strlen(filename) && filename[i] != '\0';i++){
            if(filename[i] == ' '){
                if(!flag1)break;
            }else {
                flag1 = false;
                source[j++]  = filename[i];
            }
        }
        source[j] = '\0';

        j = 0;
        for(i;i<strlen(filename) && filename[i] != '\0';i++){
            if(filename[i] == ' '){
                if(!flag2) break;
            }else {
                flag2 = false;
                dest[j++]  = filename[i];
            }
        }
        dest[j] = '\0';

       // printf("source :%s,dest %s \n",source,dest);

        if (source == NULL || strlen(source) <= 0){
            printf("cp: Source file not exists.\n");
            machine->WriteRegister(2,-1);
        }
        if (dest != NULL && strlen(dest) > 0){
            NAppend(source,dest);
            machine->WriteRegister(2,PidNum);
        }
    }else if((filename[0] == 'u' && filename[1] == 'a' && filename[2] == 'p')  // uap source dest
        || (filename[0] == 'u' && filename[1] == 'c' && filename[2] == 'p')){  // ucp source dest
        char source[128];
        char dest[128];
        bool flag1 = true;
        bool flag2 = true;
        int i;
        int j = 0;
        for( i = 3;i<strlen(filename) && filename[i] != '\0';i++){
            if(filename[i] == ' '){
                if(!flag1)break;
            }else {
                flag1 = false;
                source[j++]  = filename[i];
            }
        }
        source[j] = '\0';

        j = 0;
        for(i;i<strlen(filename) && filename[i] != '\0';i++){
            if(filename[i] == ' '){
                if(!flag2) break;
            }else {
                flag2 = false;
                dest[j++]  = filename[i];
            }
        }
        dest[j] = '\0';


        if (source == NULL || strlen(source) <= 0){
            printf("uap or ucp: Source file not exists.\n");
            machine->WriteRegister(2,-1);
        }
        if (dest != NULL && strlen(dest) > 0){
            if (filename[0] == 'u' && filename[1] == 'c' && filename[2] == 'p')
                Append(source, dest,0); //append dest file at the end of source file
            else
            Copy(source, dest);
            machine->WriteRegister(2,PidNum);
        }
        else{
            printf("uap or ucp: Missing dest file.\n");
            machine->WriteRegister(2,-1);
        }
        
    }
    else if (strstr(filename,"fdisk") != NULL) //fdisk
    {
        fileSystem->Print();
        machine->WriteRegister(2,PidNum); //
    }
    else if ((strstr(filename,"exit") != NULL) || (strstr(filename,"halt") != NULL)) //halt exit
    {
        interrupt->Halt();
    }
    else if (strstr(filename,"format") != NULL) //format
    {
        printf("strstr(filename,\"format\"=%s \n",strstr(filename,"format"));
        printf("WARNING: Format Nachos DISK will erase all the data on it.\n");
        printf("Do you want to continue (y/n)? ");
        char ch;
        while (true){
            ch = getchar();
            if (ch == 'Y' || ch == 'N')
                break;
        } //while
        if (ch == 'N')
        {
            machine->WriteRegister(2,PidNum); //
        }
        else {
            printf("Format the DISK and create a file system on it.\n");
            //fileSystem->FormatDisk(true);
            delete fileSystem;
            fileSystem = new FileSystem(true);
            machine->WriteRegister(2,PidNum); //
        }
    }else if (strstr(filename,"perf") != NULL) //Performance
    {
        PerformanceTest();
        machine->WriteRegister(2,PidNum); //

    }
    else if (filename[0] == 'm' && filename[1] == 'k' && filename[2] == 'd' && filename[3] == 'i' && filename[4] == 'r'){ // mkdir
        // MakeDir(*(argv + 1));
        char fn[128];
        strncpy(fn,filename,strlen(filename));
        fn[strlen(filename)] = '\0';
        fn[0] = ' ';
        fn[1] = ' ';
        fn[2] = ' ';
        fn[3] = ' ';
        fn[4] = ' ';
        char *file = EraseStrChar(fn,' ');
        if (file != NULL && strlen(file) >0){
            MakeDir(file);
            machine->WriteRegister(2,PidNum);
        }
        else{
            printf("mkdir failed!.\n");
            machine->WriteRegister(2,-1);
        }
    } 
    else if (strstr(filename,"help") != NULL) // help
    {
        printf("Commands and Usage:\n");
        printf("ls: list files on DISK.\n");
        printf("fdisk: display DISK information.\n");
        printf("format: format DISK with creating a file system on it.\n");
        printf("performence: test DISK performence.\n");
        printf("cf name: create a file \"name\" on DISK.\n");
        printf("cp source dest : copy Nachos file \"source\" to \"dest\".\n");
        printf("nap source dest: Append Nachos file \"source\" to \"dest\"\n");
        printf("ucp source dest : Copy Unix file \"source\" to Nachos file \"dest\"\n");
        printf("uap source dest : Append Unix file \"source\" to Nachos file \"dest\"\n");
        printf("cat name: print content of file \"name\".\n");
        printf("exit : halt!\n");
        printf("halt : halt!\n");
        printf("mkdir :create a dir by URL\n");
        machine->WriteRegister(2,PidNum);
    }
    else{
            // 执行一个 经nachos编译器编译的可执行程序
        // 读入可执行程序，并分配内存
        OpenFile *executable = fileSystem->Open(filename);
        AddrSpace *space;

        if (executable == NULL) {
            printf("Unable to open file %s\n", filename);
            machine->WriteRegister(2,-1);
        return;
        }
        space = new AddrSpace(executable); 
        space->Print(); // 输出该作业的页表信息
        delete executable;			// close file

        // 在父线程中记录子线程 pid
        for(int k = 0;k < 100;k++){
            if(currentThread->space->childPid[k] == -1){
                currentThread->space->childPid[k] = space->getSpaceID();
                break;
            }

        }



        // 创建内核线程
        Thread *t = new Thread("forked thread");
        // 将新创建的用户线和内核线程映射起来
        t->space = space; 
        // 重载 StartPorcess 进行初始化寄存器并执行可执行程序
        printf("exec Fork\n");
        t->Fork(StartProcess, space->getSpaceID());

        //return spaceID
        // 寄存器2 用于放函数的返回值
        machine->WriteRegister(2,space->getSpaceID());
    }
}

//----------------------------------------------------------------------
// Excep_SC_Exit
//  结束一个用户进程
// 调用 用户线程的析构函数，内核线程与用户线程分离，内核线程接着向下执行直到退出
//----------------------------------------------------------------------

void Excep_SC_Exit(){
    // 撤销用户线程
    delete currentThread->space;
    // 直接将当前的内核线程执行终止
     currentThread->Finish();
}


//----------------------------------------------------------------------
// Excep_SC_Join
//  等待一个用户进程执行结束
// 采用信号量 P V 操作执行
//----------------------------------------------------------------------

void Execp_SC_Join(){
    // 将要等待的进程pid读出
   
    int pid = machine->ReadRegister(4);
    if(pid >= PidNum) // pid 超出PidNum 则不执行wait
        return;

    if(pid == -1){
        // 说明执行出错
        printf("Invalid Command, Enter again or enter help \n");
        return;
    }
    // 执行信号量的P 操作
    currentThread->space->wait(pid);

}

//----------------------------------------------------------------------
// Excep_SC_Yield 
// 将当前用户进程，从运行态，强行改为就绪态
//----------------------------------------------------------------------

void Execp_SC_Yield(){
    // 当前线程的上下文切换，还是依赖内核线程的切换
    currentThread->Yield();
}


//----------------------------------------------------------------------
// Excep_SC_Create 
// 在nachos 文件系统中 创建文件
//----------------------------------------------------------------------

void Execp_SC_Create(){
    // 从寄存器中读入参数
    int base = machine->ReadRegister(4);
    int value;
    int count=0;
    char *FileName= new char[128];
    do{
        machine->ReadMem(base+count,1,&value);
        FileName[count]=*(char*)&value;
        count++;
    } while(*(char*)&value!='\0'&&count<128);

    if(!fileSystem->Create(FileName,0)) //call Create() in FILESYS,see filesys.h
        printf("create file %s failed!\n",FileName);
    else
        DEBUG('f',"create file %s succeed!\n",FileName);

}


//----------------------------------------------------------------------
// Excep_SC_Open
// 打开一个
//----------------------------------------------------------------------

void Execp_SC_Open(){
    int base = machine->ReadRegister(4);
    int value;
    int count=0;
    char *FileName= new char[128];
    do{
        machine->ReadMem(base+count,1,&value);
        FileName[count]=*(char*)&value;
        count++;
    } while(*(char*)&value!='\0'&&count<128);

    int fileid;
    // 打开文件，返回打开文件的指针char  swap[5] = {'S','W','A','P','\0'};
    OpenFile* openfile = fileSystem->Open(FileName);

    //printf("Open filename %s .\n",FileName);
    if(openfile == nullptr){
        //文件不存在
        printf("File %s not Exites,could not open it .\n",FileName);
        fileid = -1;
    }else {
        // 文件存在，将其加入进程的带开文件表
        fileid = currentThread->space->getFileDescriptor(openfile);
        // 打开文件表已满
        if(fileid < 0)
            printf("Too many files opened!\n");
        else
            DEBUG('f',"file :%s open secceed! the file id is %d\n",FileName,fileid);

    }


    // 通过寄存器2 返回值
    machine->WriteRegister(2,fileid);
 }

//----------------------------------------------------------------------
// Execp_SC_Write()
// 向 fid 对应的文件中
//----------------------------------------------------------------------

 void Execp_SC_Write(){

    //Write(char *buffer, int size, OpenFileId id);
     // 从4 5 6号寄存器 读出参数
     int base = machine->ReadRegister(4);
     int size = machine->ReadRegister(5);
     int fid = machine->ReadRegister(6);
    // printf("base=%d, size=%d, fileId=%d \n",base,size,fid);

    int value;
    int count=0;
    OpenFile* openfile;
    // 目前一次性输入的字符串大小限制在 128    
    char* buffer= new char[128];
    do{
        machine->ReadMem(base+count,1,&value);
        buffer[count] = *(char*)&value;
        count++;
    } while((*(char*)&value!='\0') && (count<size));
    buffer[size]='\0';
    
    // 不能向标准输出中写, fid要满足约束 
    ASSERT(fid >= 1 && fid <10);
    openfile = currentThread->space->getOpenFile(fid);
     // 取出打开文件指针
    if(fid == 1 || fid == 2){
        // 调用标准输出
      
        openfile->WriteStdout(buffer,size);
        return;
    }
    if(openfile == nullptr){
        // 打开文件失败
        //DEBUG('a',"SC_Write open fid %d failed!.\n",fid);
        printf("SC_Write open fid %d failed.\n",fid);
        return;
    }

    int WritePosition = openfile->Length();
    openfile->Seek(WritePosition); //append write
    //printf("write position %d \n.",WritePosition);
    // 开始执行写入
    //printf("SC_Write wirte fid %d success!\n",fid);
    if(openfile->Write(buffer,size) == size)
        DEBUG('a',"SC_Write wirte fid %d success!\n",fid);

     
 }

//----------------------------------------------------------------------
// Excep_SC_Read
// 从nachos 文件中读取到字符数组
//----------------------------------------------------------------------

void Execp_SC_Read(){
    // 当前线程的上下文切换，还是依赖内核线程的切换
    int base =machine->ReadRegister(4);
    int size = machine->ReadRegister(5);
    int fileId=machine->ReadRegister(6);
    DEBUG('a',"SC_Read buffer base %d size %d fileId %d success!\n",base,size,fileId);
    char* buffer = new char[size + 1];
    int amountRead;

    // 对读取的文件号进行限制
    ASSERT(fileId!= 1&& fileId != 2);   
    OpenFile* openfile = currentThread->space->getOpenFile(fileId);
    if(fileId == 0){
        // 从标准输入中读
        amountRead = openfile->ReadStdin(buffer,size);
        buffer[amountRead] = '\0';
       
    } else{
        if(openfile == nullptr){
        // 无法打开文件
            printf("SC_Read, open fileId %d failed.\n",fileId);
            return;
        }
        // 从0 开始读
        amountRead = openfile->ReadAt(buffer, size, 0);
        buffer[amountRead] = '\0';
    }

    // 向内存中写
    for(int i = 0;i < amountRead;i++){
        if(!machine->WriteMem(base+i,1,buffer[i]))
            printf("Something wrong when writing the mem!\n");
    }

    if(amountRead > 0 && fileId >=0 ){
        DEBUG('f',"Read file (%d) succeed! the content is \"%s\" , the length is %d\n",
        fileId,buffer,amountRead);
    }

    // 通过寄存器2 返回值
    delete buffer;
    machine->WriteRegister(2, amountRead);
    
}


//----------------------------------------------------------------------
// Excep_SC_Close
// 从打开的文件表中，删除打开文家
//----------------------------------------------------------------------


void Execp_SC_Close(){
    int fileId = machine->ReadRegister(4);
    OpenFile* openfile = currentThread->space->getOpenFile(fileId);
    if(openfile != nullptr){
        openfile->WriteBack();  // 写回磁盘，其实也没必要
        currentThread->space->releaseFileDescriptor(fileId);
        delete openfile;
    }
    else 
        printf("Fail to close file id :%d.\n",fileId);
    
}

//----------------------------------------------------------------------
// ExceptionHandler
// 	Entry point into the Nachos kernel.  Called when a user program
//	is executing, and either does a syscall, or generates an addressing
//	or arithmetic exception.
//
// 	For system calls, the following is the calling convention:
//
// 	system call code -- r2
//		arg1 -- r4
//		arg2 -- r5
//		arg3 -- r6
//		arg4 -- r7
//
//	The result of the system call, if any, must be put back into r2. 
//  注意：这里说了，系统调用的返回值一定要放在 r2 寄存器中
// And don't forget to increment the pc before returning. (Or else you'll
// loop making the same system call forever!
//
//	"which" is the kind of exception.  The list of possible exceptions 
//	are in machine.h.
//----------------------------------------------------------------------

void
ExceptionHandler(ExceptionType which)
{
    // 从2号寄存器中读出系统调用号
    int type = machine->ReadRegister(2);

    if ( which == SyscallException ) {
        switch (type)
        {
        case SC_Halt:
            DEBUG('a', "Shutdown, initiated by user program.\n");
   	        interrupt->Halt();
            break;
        case SC_Exec:
            DEBUG('a',"Exec a new user program.\n"); 
            Excep_SC_Exec();
            break;
        case SC_Exit:
            // 当用户程序执行结束 或者执行系统调用Exit 都会跳转到这里
            DEBUG('a',"Exit a user program.\n"); 
            Excep_SC_Exit();
            break;
        case SC_Join:
            DEBUG('a',"Join, waitng for a user program finish.\n"); 
            Execp_SC_Join();
            break;
        case SC_Yield:
            DEBUG('a',"Yield, turn current thread from running to ready.\n");
            Execp_SC_Yield(); 
            break;
        case SC_Create:
            DEBUG('a',"Create, create a file in nachos file system.\n");
            Execp_SC_Create();
            break;
        case SC_Open:
            DEBUG('a',"Open, open a nachos file in nachos file system and return the file id in the system file table .\n");
            // 打开文件，返回打开文件的id
            Execp_SC_Open();
            break;
        case SC_Write:
            DEBUG('a',"Wirte, wirte into a nachos file. \n");
            Execp_SC_Write();
            //testWrite();
        break;
        case SC_Read:
            DEBUG('a',"Read, read a nachos file from the begin of the file. \n");
            Execp_SC_Read();
            //testWrite();
        break;
            
        case SC_Close:
            DEBUG('a',"Close, close a file in nachos file system. \n");
            Execp_SC_Close();
        break;
        default:

             printf("unfinished sc %d\n",type);
            break;
        }
	
    }  else if(which == PageFaultException){
        // 缺页
        DEBUG('a'," Page Defult!\n");
        int virtAddr =machine->ReadRegister(BadVAddrReg);
        int vpn = (unsigned) virtAddr / PageSize;
	// 在虚拟页内的偏移
        int offset = (unsigned) virtAddr % PageSize;
        // 将缺的页从SWAP中换入
        currentThread->space->swapIn(vpn);
    } 
    else {
	printf("Unexpected user mode exception %d %d\n", which, type);
	ASSERT(FALSE);
    }
}
